package org.datascooter.bundle;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.datascooter.bundle.attribute.DBIndex;
import org.datascooter.bundle.attribute.PersistAttribute;
import org.datascooter.bundle.attribute.PersistMethodAttribute;
import org.datascooter.exception.DataScooterException;
import org.datascooter.exception.EntityNotMappedException;
import org.datascooter.generator.UUIDGenerator;
import org.datascooter.impl.DataSnip;
import org.datascooter.impl.DataSnipFactory;
import org.datascooter.inface.IDataManager;
import org.datascooter.utils.DBType;
import org.datascooter.utils.LangUtils;

public class ContainerProvider {

	private static final String ID = "id";
	private static final String CROSS = "CROSS_";
	public static final String CONTENT_ID = "contentId";
	private static final String CROSS_CN = "cross_cn_";
	public static final String CONTAINER_ID = "containerId";
	private static final String CONTAINER_ID2 = "container_id";
	private static final String CROSS_CT = "cross_ct_";
	private static final String CONTENT_ID2 = "content_id";
	private final IDataManager manager;
	private Map<String, Map<String, CrossTable>> crossMap = new ConcurrentHashMap<String, Map<String, CrossTable>>();
	private List<EntityBundle> crossList = new ArrayList<EntityBundle>();

	public ContainerProvider(IDataManager manager) {
		this.manager = manager;
	}

	public void clearCross() {
		manager.delete(CrossTable.class);
	}

	public void init() throws EntityNotMappedException, SecurityException, NoSuchMethodException {
		crossMap.clear();
		crossList.clear();
		if (manager.tableExists(CrossTable.class)) {
			List<CrossTable> list = manager.list(CrossTable.class);
			for (CrossTable table : list) {
				if (table.getContainerClass() == null || table.getContentClass() == null) {
					continue;
				}
				if (!DSMapper.isMapped(table.getContainerClass()) || !DSMapper.isMapped(table.getContentClass())) {
					continue;
				}
				try {
					addtable(table);
				} catch (Throwable e) {
					Logger.getLogger(getClass().getName()).log(Level.SEVERE, "====Problem with cross  " + table.getContainerClass() + " - " + table.getContentClass(), e);
				}
			}
		}
		for (CrossTable table : DSMapper.getCrossList()) {
			if (table.getContainerClass() == null || table.getContentClass() == null) {
				continue;
			}
			if (!DSMapper.isMapped(table.getContainerClass()) || !DSMapper.isMapped(table.getContentClass())) {
				continue;
			}
			addtable(table);
		}
	}

	private void addtable(CrossTable table) throws SecurityException, NoSuchMethodException, EntityNotMappedException {
		Map<String, CrossTable> map = crossMap.get(table.getContainerClass());
		if (map == null) {
			map = new HashMap<String, CrossTable>();
			crossMap.put(table.getContainerClass(), map);
		}
		map.put(table.getContainerAttribute(), table);
		EntityBundle bundle = createCrossBundleInt(DSMapper.getBundle(table.getContainerClass()), DSMapper.getBundle(table.getContentClass()), table.getTableName());
		crossList.add(bundle);
		DSMapper.addBundleInt(bundle);
	}

	public CrossTable getCrossTable(Object obj, String containerAttribute, Object content) throws SecurityException, NoSuchMethodException, EntityNotMappedException {
		if (obj == null) {
			return null;
		}
		CrossTable crossTable = null;
		String entityName = entityName(obj);
		Map<String, CrossTable> map = crossMap.get(entityName);
		if (map == null) {
			map = new HashMap<String, CrossTable>();
			crossMap.put(entityName, map);
		}
		crossTable = map.get(containerAttribute);
		String contentClass = entityName(content);
		if (crossTable == null && contentClass != null) {
			EntityBundle bundle = DSMapper.getBundle(entityName);
			EntityBundle bundle2 = DSMapper.getBundle(contentClass);
			if (bundle != null && bundle2 != null) {
				crossTable = createCrossTable(entityName, containerAttribute, map, bundle, bundle2);
			}
		}
		return crossTable;
	}

	public CrossTable getCrossTableOut(String containerName, String containerAttribute) {
		Map<String, CrossTable> map = crossMap.get(containerName);
		if (map == null) {
			return null;
		}
		return map.get(containerAttribute);
	}

	public Map<String, CrossTable> getCrossMapOut(String containerName) {
		return crossMap.get(containerName);
	}

	public static String entityName(Object obj) {
		if (obj == null) {
			return null;
		} else if (obj instanceof String) {
			return obj + "";
		} else if (obj instanceof Class<?>) {
			return ((Class<?>) obj).getName();
		} else if (obj instanceof DataSnip) {
			return ((DataSnip) obj).entityName;
		} else if (obj.getClass().isArray()) {
			if (Array.getLength(obj) > 0) {
				return Array.get(obj, 0).getClass().getName();
			}
		} else if (Collection.class.isAssignableFrom(obj.getClass())) {
			if (((Collection<?>) obj).size() > 0) {
				return ((Collection<?>) obj).iterator().next().getClass().getName();
			}
		} else {
			return obj.getClass().getName();
		}
		return null;
	}

	public <T> List<T> getContent(Object container, String containerAttribute) {
		try {
			List<T> result = new ArrayList<T>();
			CrossTable crossTable = getCrossTable(container, containerAttribute, null);
			if (crossTable == null) {
				return result;
			}
			EntityBundle containerBundle = DSMapper.getBundle(container);
			DataSnip select = DataSnipFactory.select(crossTable.getContentClass());
			select.join(crossTable.getContainerClass()).whereId(DSMapper.getIdValue(container));
			return manager.list(select);
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	public void saveContent(Object container, String containerAttribute, Object... content) throws EntityNotMappedException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
		for (Object item : content) {
			if (Collection.class.isAssignableFrom(item.getClass())) {
				Iterator<?> iterator = ((Collection<?>) item).iterator();
				while (iterator.hasNext()) {
					saveCross(container, containerAttribute, iterator.next());
				}
			} else {
				saveCross(container, containerAttribute, item);
			}
		}
	}

	public void saveCross(Object container, String containerAttribute, Object item) throws SecurityException, NoSuchMethodException, EntityNotMappedException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		if (item.getClass().getName().contains("$")) {
			throw new DataScooterException("***Inner class try to save as content : " + item.getClass().getName());
		}
		EntityBundle contentBundle = DSMapper.getBundle(item);
		if (contentBundle == null) {
			return;
		}
		String name = container.getClass().getName();
		if (name.contains("$")) {
			throw new DataScooterException("***Inner class try to save as container: " + name);
		}
		EntityBundle containerBundle = DSMapper.getBundle(container);
		if (contentBundle.parentEntity != null && contentBundle.parentEntity.equals(containerBundle.entity)) {
			manager.saveOrUpdate(container);
			return;
		}
		CrossTable crossTable = getCrossTable(container, containerAttribute, item);
		if (crossTable == null) {
			throw new DataScooterException("***Not found cross table");
		}
		manager.saveOrUpdate(item);
		manager.saveSet(crossTable.getTableName(), containerBundle.getId(container), contentBundle.getId(item));
	}

	public void removeContent(Object container, String containerAttribute, Object... content) {
		try {
			Map<String, CrossTable> map = crossMap.get(container.getClass().getName());
			if (map == null) {
				return;
			}
			EntityBundle containerBundle = DSMapper.getBundle(container);
			CrossTable crossTable = map.get(containerAttribute);
			if (crossTable == null) {
				return;
			}
			EntityBundle contentBundle = DSMapper.getBundle(crossTable.getContentClass());
			if (content.length == 0) {
				removeAll(container, containerBundle, crossTable);
			} else {
				for (Object data : content) {
					if (Collection.class.isAssignableFrom(data.getClass())) {
						Iterator<?> iterator = ((Collection<?>) data).iterator();
						while (iterator.hasNext()) {
							removeCross(container, containerBundle, crossTable, contentBundle, iterator.next());
						}
					} else {
						removeCross(container, containerBundle, crossTable, contentBundle, data);
					}
				}
			}
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	private void removeCross(Object container, EntityBundle containerBundle, CrossTable crossTable, EntityBundle contentBundle, Object data) throws IllegalAccessException, InvocationTargetException {
		manager.delete(crossTable.getContentClass(), contentBundle.id.name, contentBundle.getId(data));
		DataSnip where = DataSnipFactory.delete(crossTable.getTableName()).where(CONTAINER_ID, containerBundle.getId(container)).where(CONTENT_ID, contentBundle.getId(data));
		manager.delete(where);
	}

	private void removeAll(Object container, EntityBundle containerBundle, CrossTable crossTable) throws IllegalAccessException, InvocationTargetException {
		manager.delete(DataSnipFactory.delete(crossTable.getTableName()).where(CONTAINER_ID, containerBundle.getId(container)));
	}

	private CrossTable createCrossTable(String containerClass, String containerAttribute, Map<String, CrossTable> map, EntityBundle containerBundle, EntityBundle contentBundle) throws SecurityException, NoSuchMethodException, EntityNotMappedException {
		String table = CROSS + "_" + LangUtils.getRandStr(4);
		CrossTable crossTable = new CrossTable(containerClass, containerAttribute, contentBundle.entity, table);
		map.put(containerAttribute, crossTable);
		manager.save(crossTable);
		createCrossBundle(containerBundle, contentBundle, table);
		return crossTable;
	}

	public DataSnip getContentDataSnip(String parentClassName, Object parentId, EntityBundle contentBundle, Object content, String containerAttribute) {
		try {
			CrossTable crossTable = getCrossTable(parentClassName, containerAttribute, content);
			if (crossTable == null) {
				return null;
			}
			DataSnip insert = DataSnipFactory.insert(crossTable.getTableName());
			insert.what(CONTAINER_ID, parentId);
			insert.what(CONTENT_ID, contentBundle.getId(content));
			return insert;
		} catch (Exception e) {
			e.printStackTrace();
			throw new DataScooterException(e);
		}
	}

	public DataSnip getContainerDataSnip(Class<?> containerClass, Object content, String containerAttribute) {
		try {
			CrossTable crossTable = getCrossTable(containerClass, containerAttribute, content);
			if (crossTable == null) {
				return null;
			}
			EntityBundle contentBundle = DSMapper.getBundle(content);
			DataSnip snip = DataSnipFactory.count(crossTable.getTableName());
			snip.where(CONTENT_ID, contentBundle.getId(content));
			return snip;
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	public int countContent(Object container, String containerAttribute) {
		try {
			CrossTable crossTable = getCrossTable(container, containerAttribute, null);
			if (crossTable == null) {
				return 0;
			}
			EntityBundle containerBundle = DSMapper.getBundle(container);
			return manager.count(crossTable.getTableName(), CONTAINER_ID, containerBundle.getId(container));
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	public int countContainer(Class<?> containerClass, Object content, String containerAttribute) {
		try {
			CrossTable crossTable = getCrossTable(containerClass, containerAttribute, content);
			if (crossTable == null) {
				return 0;
			}
			EntityBundle contentBundle = DSMapper.getBundle(content);
			return manager.count(crossTable.getTableName(), CONTENT_ID, contentBundle.getId(content));
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	public <T> List<T> getContainerList(Class<T> containerClass, Object content, String containerAttribute) {
		try {
			CrossTable crossTable = getCrossTable(containerClass, containerAttribute, content);
			if (crossTable == null) {
				return null;
			}
			EntityBundle contentBundle = DSMapper.getBundle(content);
			DataSnip dataSnip = DataSnipFactory.select(containerClass);
			dataSnip.join(CONTAINER_ID, crossTable.getTableName(), true).where(CONTENT_ID, contentBundle.getId(content));
			return manager.list(dataSnip);
		} catch (Exception e) {
			throw new DataScooterException(e);
		}
	}

	private EntityBundle createCrossBundleInt(EntityBundle containerBundle, EntityBundle contentBundle, String table) throws SecurityException, NoSuchMethodException {
		if (contentBundle.parentEntity != null && contentBundle.parentEntity.equals(containerBundle.entity)) {
			return containerBundle;
		}
		if (contentBundle.id == null) {
			contentBundle.setId(new PersistMethodAttribute(ID, ID, DBType.CHAR, 36, 0, true, false, false));
			contentBundle.setIdGenerator(new UUIDGenerator());
		}
		if (containerBundle.id == null) {
			containerBundle.setId(new PersistMethodAttribute(ID, ID, DBType.CHAR, 36, 0, true, false, false));
			containerBundle.setIdGenerator(new UUIDGenerator());
		}
		EntityBundle bundle = new EntityBundle(table, null, table, false, false, CrossData.class);
		PersistAttribute copy = containerBundle.id.copy();
		copy.name = CONTAINER_ID;
		copy.setColumn(CONTAINER_ID2);
		bundle.addPersistAttribute(copy);
		DBIndex dbIndex = new DBIndex(CROSS_CT + LangUtils.getRandomIndexString(4), null);
		dbIndex.addAttribute(copy);
		bundle.addIndex(dbIndex);
		copy = contentBundle.id.copy();
		copy.name = CONTENT_ID;
		copy.setColumn(CONTENT_ID2);
		bundle.addPersistAttribute(copy);
		dbIndex = new DBIndex(CROSS_CN + LangUtils.getRandomIndexString(4), null);
		dbIndex.addAttribute(copy);
		bundle.addIndex(dbIndex);
		return bundle;
	}

	private void createCrossBundle(EntityBundle containerBundle, EntityBundle contentBundle, String table) throws SecurityException, NoSuchMethodException, EntityNotMappedException {
		EntityBundle bundle = createCrossBundleInt(containerBundle, contentBundle, table);
		DSMapper.addBundleInt(bundle);
		manager.verifyTable(bundle);
	}

	public List<EntityBundle> getCrossList() {
		return crossList;
	}
}
